package biolabs.model.growth

import biolabs.core.{BioObject, Interaction, WrappedDelegate}
import biolabs.model.GridBioContainer
import biolabs.space.ContinuousPosition
import biolabs.space.DiscretePosition
import biolabs.util.Concurrency

import java.util.Iterator
import java.util.LinkedList

import scala.collection.JavaConversions._

/**
 * A trait of bio-objects that can perform growth interaction
 * of their children.
 *
 * @author Tomas Mikula
 */
class Growth[
	T <: BioObject with Growable[T, P],
	P <: DiscretePosition[P, C],
	C <: ContinuousPosition[C, P]
] extends Interaction[GridBioContainer[T, P, C], Unit, Unit, GrowthDelegate[P], T] {

	type Parent = GridBioContainer[T, P, C]

	/**
	 * Performs the growth of child bio-objects.
	 */
	override def perform(
			parent: Parent,
			participants: Iterable[WrappedDelegate[GrowthDelegate[P], T]],
			notifyProgress: Unit=>Unit
	): Unit = {

		val growDescs = new LinkedList(participants map ((p)=>(p.owner, p.elements.iterator())))

		while(!growDescs.isEmpty()) {
			val g = growDescs.iterator

			// grow each cell by at most one element
			while(g.hasNext()) {
				val (child, toGrow) = g.next()
				if(!captureNextPos(parent, child, toGrow))
					g.remove()
			}
		}

		Concurrency.commit(participants);
	}

	private def captureNextPos(parent: Parent, child: T, it: Iterator[P]): Boolean = {
		while(it.hasNext()) {
			val pos = it.next()
			if(capturePosition(parent, child, pos))
				return true
			else
				it.remove();
		}
		return false
	}

	/**
	 * Tries to capture the position {@code pos} by the given {@code child}
	 * bio-object. {@code pos} will be captured if and only if it is in range,
	 * not occupied yet and has a neighbor already occupied by {@code child}.
	 * @return {@code true} if the position was actually captured,
	 * {@code false} otherwise.
	 */
	private def capturePosition(parent: Parent, child: T, pos: P): Boolean = {
		if(!parent.isValidPos(pos))
			return false;
		if(parent.getChildAt(pos) != null) // position already occupied
			return false;
		if(!hasNeighbor(parent, pos, child)) // child would become disconnected
			return false;
		parent.setChildAt(pos, child);
		parent.notifyCapture(pos)
		return true;
	}

	/**
	 * Returns {@code true} if the position {@code pos} has a neighbor
	 * occupied by {@code child}, {@code false} otherwise.
	 */
	private def hasNeighbor(parent: Parent, pos: P, child: BioObject): Boolean = {
		val it = pos.getNeighbors().iterator()
		while(it.hasNext()) {
			val np = it.next()
			if(parent.isValidPos(np) && (parent.getChildAt(np) eq child))
				return true;
		}
		return false;
	}

}
